package cs235.sort;

import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

public class AnalyzerImpl implements Analyzer {
    private BigO selected;
    private Map<BigO, Map<String, List<Double>>> results;
    private String ratios ="ratios";
    private double error=-1;
    private String errors = "errors";
    
    
    public static enum BigO{
        N(0, "O(N)"),
        N3(1, "O(N^3)"),
        N2(2, "O(N^2)"),
        _2N(3, "O(2^N)"),
        LOG_N(4, "O(log N)"),
        N_LOG_N(5, "O(N log N)"),
        _1(6, "O(1)");
        
        private String text;
        private int position;
        
        private BigO(int position, String text){
            this.text = text;
            this.position = position;
        }
        
        public int position(){
            return position;
        }
        
        @Override
        public String toString(){
            return text;
        }
        
        private static Map<Integer, BigO> lookupByCode = new HashMap<Integer, BigO>();
        
        static {
            for (BigO p : EnumSet.allOf(BigO.class))
            {
                lookupByCode.put(p.position(), p);
            }
        }
        
        public static BigO getBigO(Integer code){
            if (lookupByCode.containsKey(code)){
                return lookupByCode.get(code);    
            }
            return null;
        }
    }

    @Override
    public void analyze(int[] sizes, long[] data) {
        if (sizes.length != data.length){
            throw new IllegalArgumentException("sizes and data should be the same length");
        } else if (sizes.length < 1){
            throw new IllegalArgumentException("The arrays don't contain any values");
        }
        results = new HashMap<BigO, Map<String, List<Double>>>();
        for (BigO b : EnumSet.allOf(BigO.class)){
            results.put(b, new HashMap<String, List<Double>>());
            results.get(b).put(ratios, new ArrayList<Double>());
        }
        for (int i=0;i<sizes.length;i++){
//            System.out.println("Sizes["+i+"]:"+sizes[i]+"\tData["+i+"]:"+data[i]);
            calculateBigOhs(sizes[i], data[i]);
        }
        Iterator<BigO> itr = results.keySet().iterator();
        double smallest = Integer.MAX_VALUE;
        while(itr.hasNext()){
            BigO current = itr.next();
            List<Double> temp = results.get(current).get(ratios);
            double mean = meanList(temp);
            List<Double> errors = new ArrayList<Double>(temp.size());
            for (int i =0;i<temp.size();i++){
                errors.add(Math.abs(temp.get(i) - mean)/mean);
            }
            double error = meanList(errors);
            results.get(current).put(this.errors , errors);
//            System.out.println(current + " has an error of: " + error);
            if (error < smallest){
                this.error = error;
                smallest=error;
                selected = current;
            }
        }
    }
    
    private Double meanList(List<Double> list){
        return sumList(list) / list.size();
    }
    
    private Double sumList(List<Double> list){
        double sum = 0;
        for (Double next : list){
            sum += next;
        }
        return sum;
    }
    
    private void calculateBigOhs(int size, long data){
        results.get(BigO.N).get("ratios").add(Math.pow(size, 1) / data);
        results.get(BigO.N2).get("ratios").add(Math.pow(size, 2) / data);
        results.get(BigO.N3).get("ratios").add(Math.pow(size, 3) / data);
        results.get(BigO._2N).get("ratios").add(Math.pow(2, size) / data);
        results.get(BigO.LOG_N).get("ratios").add(Math.log(size) / data);
        results.get(BigO.N_LOG_N).get("ratios").add((size * Math.log(size)) / data);
        results.get(BigO._1).get("ratios").add(1.0 / data);
    }

    @Override
    public double[] getRatios() {
        if (results == null ){
            throw new IllegalStateException("Analyze needs to be called first");
        }
        List<Double> tmp = results.get(selected).get(ratios);
        double[] temp = new double[tmp.size()];
        for (int i=0;i<temp.length;i++){
            temp[i] = tmp.get(i);
        }
        return temp;
    }

    @Override
    public double getError() {
        if (error == -1){
            throw new IllegalStateException("Analyze needs to be called first");
        }
        return error;
    }

    @Override
    public String getBigOh() {
        if (selected==null){
            throw new IllegalStateException("Analyze needs to be called first");
        }
        return selected.toString();
    }

}
